/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package tuplestoremasterserver;

import core.Callback;
import core.IServerCallBack;
import core.ProtocolSD;
import core.Result;
import core.ServerCallBack;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.util.LinkedList;
import java.util.List;
import logging.LOG;
import protocol.TSServerInfo;

/**
 *
 * @author Jorge
 */
public class ServerServerListener implements ProtocolSD.IProtocolListener {

    private boolean close = false;
    private ProtocolSD.Operation operation;
    private List<Integer> currentKeys = new LinkedList<Integer>();
    private int serverKey;
    private int ticket;
    private Long avalilableSize = null;
    private Integer comunicationPort = 0;
    private Long keySize = null;
    private String serverAddress = null;
    private Integer serverPort = null;
    private Integer uniqueId = 0;

    private boolean flag=false;
    private Long usedSize = null;

    public void onReadHeader(int operation, int numArgs, ProtocolSD protocolSD) {
        this.operation = ProtocolSD.Operation.values()[operation];
        LOG.append("Recebido pedido de operação "+this.operation.name());
        currentKeys.clear();
        serverKey = 0;


    }

    public void onReadArgument(byte[] data, ProtocolSD protocolSD) {
        switch (operation) {
            case TICKET:
                handlerTicket(data, protocolSD);
                break;
            case COMMIT:
                handlerCommit(data, protocolSD);
                break;
            case STATUS:
                handlerReadArgumentStatus(data, protocolSD);
                break;
        }
    }

    private void handlerReadArgumentStatus(byte[] data, ProtocolSD protocolSD) {
        TSServerInfo.FIELD field = TSServerInfo.FIELD.values()[(int) data[0]];


        switch (field) {
            case AVAILABLE_SIZE:
                avalilableSize = ProtocolSD.byteArrayToLong(data, 1);
                break;
            case COMUNICATION_PORT:
                comunicationPort = ProtocolSD.byteArrayToInt(data, 1);
                break;
            case KEY_SIZE:
                keySize = ProtocolSD.byteArrayToLong(data, 1);
                break;
            case SERVER_ADDRESS:
                serverAddress = new String(ProtocolSD.subArray(data, 1, data.length));
                break;
            case SERVER_PORT:
                serverPort = ProtocolSD.byteArrayToInt(data, 1);
                break;
            case UNIQUE_ID:
                uniqueId = ProtocolSD.byteArrayToInt(data, 1);
                break;
            case USED_SIZE:
                usedSize = ProtocolSD.byteArrayToLong(data, 1);
                break;
        }

    }

    private void handlerReadAllArgumentStatus(ProtocolSD protocolSD) {
        TSServerInfo serverInfo = null;
        if (uniqueId == null || uniqueId == 0) {
            //gerar id novo
            serverInfo = new TSServerInfo(0, serverAddress, comunicationPort, serverPort);
            if(avalilableSize!=null)
            serverInfo.setAvailableSize(avalilableSize);
            if(keySize!=null)
            serverInfo.setKeySize(keySize);
            if(uniqueId!=null)
            serverInfo.setUniqueID(uniqueId);
            if(usedSize!=null)
            serverInfo.setUsedSize(usedSize);
            if(serverInfo!=null)
            MasterServer.addServer(serverInfo);
            Callback<Integer,Integer> callback =  new Callback<Integer, Integer>() {

                public void onError(Exception e) {
                    throw new RuntimeException();
                }

                public void onResult(Result<Integer, Integer> v) {
                    MasterServer.updateKeys(v.getMap());

                }
            };

            MasterServer.getInstance().connect(serverInfo.getServerAddress(), serverInfo.getComunicationPort(), new ServerCallBack<Integer,Integer>(callback) {

                public void onFinishClient(ProtocolSD protocolSD) {
                    protocolSD.write(new byte[]{(byte)ProtocolSD.Operation.KEYSET.ordinal(),0});

                }

                @Override
                public void onWriteComplete(ProtocolSD protocolSD) {
                    protocolSD.getKey().interestOps(SelectionKey.OP_READ);
                }

                @Override
                public void onReadArgument(byte[] data, ProtocolSD protocolSD) {
                   this.onNewResult(ProtocolSD.byteArrayToInt(data,0),ProtocolSD.byteArrayToInt(data, 4));

                }

                @Override
                public void onReadAllArguments(ProtocolSD protocolSD) {
                    this.onFinish();
                }

            });
        } else {
            //getId
            MasterServer.updateServer(uniqueId, avalilableSize, keySize, usedSize);
        }

        protocolSD.write(new byte[]{(byte) ProtocolSD.Operation.STATUS.ordinal(), (byte) 1});
        protocolSD.writeInt(4);
        protocolSD.writeInt(serverInfo.getUniqueID());
    }

    public void onReadArgumentSize(int size, ProtocolSD protocolSD) {
    }

    public void onReadAllArguments(ProtocolSD protocolSD) {
        protocolSD.getKey().interestOps(SelectionKey.OP_WRITE);
        switch (operation) {
            case TICKET:
                handlerCompleteTicket(protocolSD);
                break;
            case COMMIT:
                handlerFinishCommit(protocolSD);
                //Para bloquear enquanto espera por resultado de remove
                if(!flag){
                    flag=false;
                    protocolSD.getKey().interestOps(SelectionKey.OP_READ);
                }
                break;
            case STATUS:
                handlerReadAllArgumentStatus(protocolSD);
                break;
        }
    }

    public void onWriteComplete(ProtocolSD protocolSD) {
        switch (operation) {
            case TICKET:
            case COMMIT:
            case STATUS:
                close = true;
                try {
                    protocolSD.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
        }
    }

    public void setCallBack(IServerCallBack callback) {
    }

    public IServerCallBack getCallBack() {
        return null;
    }

    public void onReadParcialParam(byte[] readedBytes, int offset, int limit, ProtocolSD protocolSD) {
    }

    public void onAcceptConnect() {
    }

    public boolean isClose() {
        return close;
    }

    private void handlerTicket(byte[] param, ProtocolSD protocolSD) {
        if (serverKey == 0) {
            serverKey = ProtocolSD.byteArrayToInt(param, 0);
        } else {
            currentKeys.add(ProtocolSD.byteArrayToInt(param, 0));
        }
    }

    private void handlerCompleteTicket(ProtocolSD protocolSD) {
        int ticket = MasterServer.getTicket();

        

        List<Integer> tmpTmp = new LinkedList<Integer>();
        for (Integer integer : currentKeys) {
            tmpTmp.add(ticket);
        }

        protocolSD.write((byte) ProtocolSD.Operation.TICKET.ordinal(), currentKeys, tmpTmp);
    }

    private void handlerCommit(byte[] bs, ProtocolSD protocolSD) {
        currentKeys.add(ProtocolSD.byteArrayToInt(bs, 0));
        serverKey = ProtocolSD.byteArrayToInt(bs, 4);
        ticket = ProtocolSD.byteArrayToInt(bs, 8);
    }

    private void handlerFinishCommit(final ProtocolSD protocolSD) {

        TSServerInfo server = MasterServer.getServer(serverKey);
        Callback<Integer, byte[]> callback = new Callback<Integer, byte[]>() {

            public void onError(Exception e) {
                throw new RuntimeException();
            }

            public void onResult(Result<Integer, byte[]> v) {
                byte b = (byte)v.getMap().get(currentKeys.get(0))[0];
                if (b == ProtocolSD.SUCCESS) {

                    protocolSD.write(new byte[]{(byte) ProtocolSD.Operation.COMMIT.ordinal(), (byte) 1});
                    protocolSD.writeInt(5);
                    protocolSD.writeInt(currentKeys.get(0));
                    protocolSD.write((byte) 1);

                } else {
                    protocolSD.write(new byte[]{(byte) ProtocolSD.Operation.REMOVE.ordinal(), (byte) 1});
                    protocolSD.writeInt(5);
                    protocolSD.writeInt(currentKeys.get(0));
                    protocolSD.write((byte) 0);
                }
                if(v.getMap().size()==2)
                    flag=true;

                    protocolSD.getKey().interestOps(SelectionKey.OP_WRITE);
                
                protocolSD.getKey().selector().wakeup();
            }
        };

        MasterServer.commitKey(currentKeys.get(0), server, ticket, callback);

    }
}
